#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <string.h>


extern char const *ptsname(int);
extern int posix_openpt(int);
extern int grantpt(int);
extern int unlockpt(int);

static
int event;

struct
{
  int help;
  int f_err;
  int verbose;
  int no_echo;
  int row, col;
} static options;

static
void
on_hup(int __)
{
  int a = write(event, "a", 1);
  (void)a;
}

static
int
transfer(int from, int to)
{
  char buf[2048];
  ssize_t r = read(from, buf, sizeof(buf));
  if (r < 0)
    return 1;

  if (options.verbose)
    {
      if (from == 0)
        fprintf(stderr, ">>>> %zd\n", r);
      else
        fprintf(stderr, "<<<< %zd\n", r);
    }
  
  ssize_t w = 0;
  while (w < r)
    {
      ssize_t n = write(to, buf + w, r - w);
      if (n <= 0)
        return 1;

      w += n;
    }
  return 0;
}

static
void
giveup_tty()
{
  int tty = open("/dev/tty", O_RDWR);
  if (tty >= 0)
    {
      ioctl(tty, TIOCNOTTY);
      close(tty);
    }
}

static
void
set_tty(int fd)
{
  setsid();
  ioctl(fd, TIOCSCTTY, 0);
}

static
void
set_noecho(int fd)
{
  struct termios ts, ts_old;
  tcgetattr(fd, &ts_old);
  ts = ts_old;
  ts.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL);
  tcsetattr(fd, TCSAFLUSH, &ts);
}

static
void
set_winsize(int fd, int row, int col)
{
  struct winsize size;
  size.ws_row = row;
  size.ws_col = col;
  size.ws_xpixel = size.ws_ypixel = 0;
  ioctl(fd, TIOCSWINSZ, &size);
}

static
void
cp_winsize(int src, int dst)
{
  struct winsize size;
  ioctl(src, TIOCGWINSZ, &size);
  ioctl(dst, TIOCSWINSZ, &size);
}

static
void
usage()
{
  char const *lang = getenv("LANG");
  if (!lang)
    lang = "en";

  if (strncmp(lang, "zh", 2) == 0)
    {
      printf("%s", 
             "用法: [- 选项] [命令 [参数]...]\n"
             "选项:\n"
             "     h: 显示帮助\n"
             "     v: 在标准错误输出调试日志\n"
             "     2: 不将标准错误连接pts\n"
             "     n: 设置pts无回显\n"
             "     s: 设置终端窗口大小(RxC)\n"
             );
    }
  else
    {
      printf("%s", 
             "Usage: [- OPTIONS] [CMD [ARG]...]\n"
             "OPTIONS:\n"
             "     h: show help\n"
             "     v: output debug log at stderr\n"
             "     2: donot connect stderr to pts\n"
             "     n: set pts no-echo\n"
             "     s: set win size of pts(RxC)\n"
             );
    }
}

static
char **
parse_args(char **argv)
{
  char *a;
  for (; (a = *argv) && *a++ == '-'; ++argv)
    {
      if (!*a)
        {
          ++ argv;
          break;
        }

      char b;
      int loop = 1;
      while (loop && (b = *a++))
        {
          switch (b)
            {
            case 'h':
              options.help = 1;
              goto end;

            case 'v':
              options.verbose = 1;
              break;

            case '2':
              options.f_err = 1;
              break;

            case 'n':
              options.no_echo = 1;
              break;

            case 's':
              sscanf(a, "%dx%d", &options.row, &options.col);
              loop = 0;
              break;

            default:
              fprintf(stderr, "option error: %c\n", b);
              break;
            }
        }
    }
 end:
  return argv;
}

int
main(int argc, char **argv)
{
  if (argc < 2)
    {
      usage();
      return 1;
    }
  memset(&options, 0, sizeof(options));
  argv = parse_args(argv + 1);
  if (options.help)
    {
      usage();
      return 0;
    }
  if (!argv[0])
    {
      usage();
      return 1;
    }
  
  int mts = posix_openpt(O_RDWR);
  if (mts < 0)
    {
      fprintf(stderr, "posix_openpt error\n");
      return 1;
    }
  grantpt(mts);
  unlockpt(mts);
  int pts = open(ptsname(mts), O_RDWR);
  if (pts < 0)
    {
      fprintf(stderr, "open pts error\n");
      return 1;
    }
  if (options.no_echo)
    set_noecho(pts);

  if (options.row > 0 && options.col)
    set_winsize(pts, options.row, options.col);
  else
    {
      int cty = open("/dev/tty", O_RDWR);
      if (cty >= 0)
        {
          cp_winsize(cty, pts);
          close(cty);
        }
    }
  
  int pid = fork();
  if (pid == 0)
    {
      dup2(pts, 0);
      dup2(pts, 1);

      if (options.f_err == 0)
        dup2(pts, 2);
      
      giveup_tty();
      set_tty(pts);
      close(pts);
      close(mts);
      execvp(argv[0], argv);
      return 127;
    }
  else
    {
      close(pts);
      event = eventfd(0, 0);
      signal(SIGCHLD, on_hup);
      while (1)
        {
          struct pollfd f[3];
          f[0].fd = event;
          f[0].events = POLLIN;
          f[0].revents = 0;
          f[1].fd = mts;
          f[1].events = POLLIN;
          f[1].revents = 0;
          f[2].fd = 0;
          f[2].events = POLLIN;
          f[2].revents = 0;
          
          int c = poll(f, sizeof(f)/sizeof(f[0]), 1000*60);
          if (c > 0)
            {
              if (f[0].revents & POLLIN)
                break;
              
              if ((f[1].revents & (POLLERR|POLLHUP)) ||
                  (f[2].revents & (POLLERR|POLLHUP)))
                break;

              if ((f[1].revents & POLLIN) && transfer(mts, 1))
                break;
              if ((f[2].revents & POLLIN) && transfer(0, mts))
                break;
            }
          else if (c < 0)
            break;
        }
      return 0;
    }
}
